/** @license Task Query Library v.0.1
 * Copyright (c) 2010 Fedor Indutny
 * Dual licensed under the MIT and GPL licenses.
 *
 * MIT License:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * GPL License:
 *
 * http://www.opensource.org/licenses/gpl-2.0.php
 * Date: 2010-01-07 00:08:21 +0600*/
(function () {
  window.tQueue=function () {
                                        // Step function that is calling every "x" ms
    function step() {
                                        // Cache index and current queue element
        var cached_index=_this.index();
        var cached_q=_this.q[cached_index];
                                        // Validate
        if (!cached_q) 
          return;
                                        // If beign used - return
        if (cached_q.block) {
          var x;
          for (x in _this.q)
            if (!_this.q[x].block) {
              cached_index=x;
              cached_q=_this.q[x];
              break;
            }
          if (cached_q.block) return;
        }
                                        // Get code to evaluate
        var code=cached_q.events[cached_q.index];
                                        // Get result
        var r=eval(code);code=null;
                                        // Local function to access local variables
        function step_end () {
                                        // Unset block status of element
          cached_q.block=null;
                                        // If called outhere then it must be this.ret
          var r=(arguments.length)?arguments[0]:this.ret;
                                        // If null then this is end of queue
          if (r==null) {
            _this.replace(cached_index);        
            return;
          }
                                        // If no events then return;
          if (cached_q.events.length<=0) return;
                                        // Go to next position
                                        // Current + (this.ret or arg[0])
          cached_q.index+=r;
                                        // Trim position
          while (cached_q.index<0) cached_q.index+=cached_q.events.length;
          cached_q.index%=cached_q.events.length;
        }
                                        // If result of evaluation is object
                                        // Than we have a result from a subquery initializing element
        if (r)
        if (typeof r=='object') {
                                        // So block this element
          cached_q.block=1;
                                        // And add hook to end
          r.hook_empty=step_end;
          return;
        }
                                        // Or normal variant -> pass result to step_end
        step_end(r);
    };
                                        // Initial id
    this.id=-1;
                                        // If not main                                                                                                
    if (window.$$)
      $$.register(this);
                                        // Public step
    this.step=step;
                                        // Local variable
    var _this=this;
                                        // Look down please ))
    this.clean();
                                        // Stop interval if it is started
    this.stop();
                                        // Default interval time
    var b=1000;
                                        // Can be not default
    if (arguments.length)
      if (typeof(arguments[0])=='number')
        if (arguments[0]>0)
          b=arguments[0];
                                        // Setup timer
    this.qtimer=setInterval(step,b);
  };
                                        // Returns index to start queue from
    window.tQueue.prototype.index= function () {
      return 0;
    };
                                        // Returns queue length
    window.tQueue.prototype.length= function () {return this.q.length};
                                        // Replaces empty element with one from end
    window.tQueue.prototype.replace= function (i) {
      var t;
      if (this.q.length>0) {
        t=this.q.pop();
        if (this.hook_pop)
          this.hook_pop();  
      }
      if (this.q.length<=i)
        return;
      this.q[i]=t;
    };
                                        // Trim ; from the end of line
    function $$trim(s) {
      return s.replace(/(;)*$/g,'');
    }

                                        // Push new element into queue

    window.tQueue.prototype.push= function () {
      var t=[];
      var p=arguments;
      if (typeof arguments[0] == 'object')
        if (arguments[0].length)
          p=arguments[0];
      if (!p.length) return;
      var x;
      for(x=0;x<p.length;x++)
        if (p[x]) {
                                        // In all cases we must append local $$ to the start of eval element
        switch(typeof(p[x])) {
                                        // Convert function to string
          case 'function':
            t.push('(function() {var $$=window.$$.ids('+this.id+'); return ('+$$trim(p[x].toString())+');})()');
            break;
                                        // No need to convert it
          case 'string':
            t.push('(function() {var $$=window.$$.ids('+this.id+'); return ('+$$trim(p[x])+');})()');
            break;
                                        // This is probably subquery
          default:
            t.push('(function() {var $$=window.$$.ids('+this.id+'); return('+this.template.toString()+')('+JSON.stringify(JSON.stringify(p[x]))+');})()');
            break;
        }           
        }
      this.overall+=t.length;
                                        // Pushes this into q
      if (t.length>0) this.q.push({events:t,index:0});
    };
                                        // Template function for subquery insertion
                                        // Can be replaced by user
    window.tQueue.prototype.template = function (queue) {
      var $$=new tQueue;
      var vars=eval(queue);
      switch (typeof(vars)) {
        case 'object':
          if (vars.length) {
            $$.push(vars);
          } else $$.push(vars.toString());
          break;
        default:
         if (vars.toString)
           $$.push(vars.toString());
         break;
      }
      return $$;
    }
                                        // Get percent of Queue complition
                                        // Doesnt extend to subqueries by now
    window.tQueue.prototype.percent = function () {
      var c=0;
      for (x in this.q)
        c+=this.q[x].events.length+1;
      return c/((this.overall!=0?this.overall:1))
    };
                                        // Stop interval timing
    window.tQueue.prototype.stop= function () {
      clearInterval(this.qtimer);
    };
                                        // Get $$ child by id
                                        // Or get new id for probably child
    window.tQueue.prototype.ids = function () {
      if (arguments.length!=0) return this.child[arguments[0]];
      return this.child.length;
    };
                                        // Register child
    window.tQueue.prototype.register = function (obj) {
      var ids=window.$$.ids();
      window.$$.child[ids]=obj;
      obj.id=ids;
      return ids;
    };
    window.tQueue.prototype.clean = function (obj) {
                                        // Main variables
      this.q=[];
      this.qtimer=0;
      this.overall=0;
                                        // Hook to empty state of task queue
      this.hook_empty=function () {}
                                        // SubQueue return
      this.ret=null;
                                        // Queue children, empty for all except $$
      this.child=[];
    };
                                        // Register base object
//    window.$$=new tQueue;
})();